//AUTHOR: Praveen Kambam Sugavanam
//CS5103 - Proj 1
//Last updated: 02/17/2012 - 3:39am
//


#include <stdio.h>
//#include <iostream>
#include <stdlib.h>
#include <sys/types.h>
#include <pthread.h>
#include <unistd.h>
#include "scheduler.cpp"
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>

void* mainOSThread(void* ptr);
void* charIOThread(void* ptr);
void* blockIOThread(void* ptr);

void addToCharIOQueue(int pid);
void addToBlockIOQueue(int pid);
int removeFromCompletedIOQueue();
//node for maintaining the pid queue within the IO threads.
typedef struct node 
{
     int pid;
     node* next;
}node ;

typedef struct node_holder
{
     node* head;
     node* tail;
}node_holder;

pthread_mutex_t mutex1 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex2 = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_t mutex3 = PTHREAD_MUTEX_INITIALIZER;

bool charIO=false,blockIO=false; 

int doCharIO=0;
//mode : kernel/user
int mode; int schedulingPolicy;
//queues for the charIO and block IO threads.
node* charIOHead = NULL, *charIOTail = NULL, *completedHead = NULL, *completedTail = NULL;
node* blockIOHead = NULL,*blockIOTail = NULL;

// timer delays
const int clockDelay = 250000;
const int charIODelay = clockDelay*4,blockIODelay = clockDelay*8;


int main(int argc, char* argv[])
{

     pthread_t thread1, thread2, thread3;
     char *message1 = "Thread 1";
     char *message2 = "Thread 2";
     int  iret1, iret2,iret3;
     //start top with fork and exec
     pid_t child_pid = fork();
     if(child_pid == 0) 
     {
       //char argv[1] = {"top"};
       char *const paramList[] = {"top"};
       //child
       execv("top", paramList);
       printf("Unknown command\n");
       exit(0);
     }
     else {

    // check for scheduling policy in the args
    if (argc == 3)
    {
      if (strcmp(argv[1],"-scheduling") == 0)
      {
        if ( strcmp(argv[2],"lottery") == 0 )
          schedulingPolicy = 1;
        else 
          schedulingPolicy = 0;        
      }
    }
    else
      schedulingPolicy = 0;
    
    /* Create independent threads each of which will execute function */
    

     iret1 = pthread_create( &thread1, NULL, mainOSThread, (void*) message1);
     iret2 = pthread_create( &thread2, NULL, charIOThread, (void*) message1);
     iret3 = pthread_create( &thread3, NULL, blockIOThread, (void*) message1);     

     
     pthread_join( thread1, NULL);
    
     return 0;
     }
}




void* mainOSThread(void* ptr)
{
   
  cout << endl;
  cout << "Starting..." << endl;
  char trace[256] = "main.trace";
  SchedulerReturn sr;
  sr.return_code = SRET_SUCCESS;
  Scheduler* s = new Scheduler(schedulingPolicy);
  int i = 0;
  Process* p = new Process(0,5,i,trace);
  s->AddProcess(0, p);

  //printf("Main thread\n");
  int fd;
  char * myfifo = "/tmp/myfifo";
  mkfifo(myfifo, 0666);
  fd = open(myfifo, O_WRONLY);
 
  

  while (sr.return_code != SRET_NO_PROCESSES)
  {
     // check if completed list is empty. if not, notify scheduler.
     if (completedHead != NULL)
     {
       int pid_done = removeFromCompletedIOQueue();
       s->UnblockProcess(pid_done);
     }
     sr = s->run(i);
     //cout << "  Started: " << sr.start_time << endl;
     //cout << "  Cycles Completed: " << sr.cycles_completed << endl;

     //cout << "Running Process:" << endl;
     //cout << "   Pid = " << sr.pid << endl;
     //cout << "   Priority = " << sr.priority << endl;
     //cout << "   File Name = " << sr.file_name << endl;
     //cout << "   Return Code = " << sr.return_code << endl;
     
     //cout << "Run output: " << sr.return_code << endl;
     char writeTop[1024];
     if ( sr.return_code == 4)
     {
        sprintf(writeTop,"No active processes currently, Clock tick : %d",i);
        write(fd,writeTop,sizeof(writeTop));
     }
     else
     {
        sprintf(writeTop,"--------------------------------------------------------------------\n");
        write(fd,writeTop,sizeof(writeTop));
        sprintf(writeTop,"Pid\tStart_Time\tFile_Name\tCycles_Completed\tStatus\tClock_Tick\n");
        write(fd,writeTop,sizeof(writeTop));                
        sprintf(writeTop,"%d\t%d\t%s\t %d\t Running\t %d \n",sr.pid,sr.start_time,sr.file_name,sr.cycles_completed,i);
        write(fd,writeTop,sizeof(writeTop));
        
        map<int,Process*> readyProcs = s->getActiveProcesses();
        map<int,Process*> blockedProcs = s->getWaitingProcesses();
        
        for (map<int,Process*>::iterator it = readyProcs.begin();it != readyProcs.end(); it++)
        {
          
          Process* p = (*it).second;
          char* procInfo = p->getProcInfo();
          
          sprintf(writeTop,"%s Ready\t %d\n",procInfo,i);
          write(fd,writeTop,sizeof(writeTop));
        }
        for (map<int,Process*>::iterator it = blockedProcs.begin();it != blockedProcs.end(); it++)
        {
          
          Process* p = (*it).second;
          char* procInfo = p->getProcInfo();
          
          sprintf(writeTop,"%s Blocked\t %d\n",procInfo,i);
          write(fd,writeTop,sizeof(writeTop));
        }        
        
        

     }
     if (sr.return_code == SRET_SYSCALL){
     // switch on sr.vals.syscall.bc
       char type = sr.vals.sysCall.bc[0];
       //sprintf(writeTop,"Current process : %d. IO Call, Clock tick : %d\n",sr.pid,i);
       //write(fd,writeTop,sizeof(writeTop));
       //cout << "Call type :" << sr.vals.sysCall.bc << endl;
       //cout << "Add to IO queue: " << sr.vals.sysCall.pid << endl;
       if (type == 'B'|| type == 'b')
         addToBlockIOQueue(sr.vals.sysCall.pid);
       else if (type == 'C' || type =='c')
         addToCharIOQueue(sr.vals.sysCall.pid);       
     }
     else if (sr.return_code == SRET_NEW_PROCESS){
       Process* p = new Process(sr.vals.newProcess.pid,sr.vals.newProcess.priority,i,sr.vals.newProcess.file_name);
       s->AddProcess(sr.vals.newProcess.pid, p);
       //sprintf(writeTop,"Current process : %d. New Process creation: %d, Clock tick : %d\n",sr.pid,sr.vals.sysCall.pid,i);
       //write(fd,writeTop,sizeof(writeTop));
       cout << "Create new process: pid = " << sr.vals.newProcess.pid << endl;
     }
     usleep(clockDelay);
     if (sr.return_code == SRET_NO_PROCESSES)
     	write(fd, "End", sizeof("End"));

     printf("Clocktick: %d\n\n",i);
     // cout << endl;
     i++;
  }
  close(fd);
  unlink(myfifo);

}


node_holder* addToQueue(node* head,node* tail, int pid)
{
       node_holder* nodeHolder = new node_holder();
       node* newNode = new (node);
       newNode->pid = pid;
       newNode->next = NULL;
       if ( head == NULL)
       {
         head = tail = newNode;
       }
       else
       {
         tail->next = newNode;
         tail = newNode;
       }

       nodeHolder->head = head;
       nodeHolder->tail = tail;
       return nodeHolder;
}

int removeFromQueue(node* head, node* tail)
{
       int returnVal;
       if (head == NULL)
         return -1;
       else
       {
         node*n = head;
         head = head->next;
         returnVal = n->pid;
         delete n;
       }
       
       return returnVal;
}


void addToCharIOQueue(int pid)
{
     pthread_mutex_lock( &mutex1 );
     node_holder* nh;
     nh = addToQueue(charIOHead,charIOTail,pid);
     charIOHead = nh->head;
     charIOTail = nh->tail;
     delete nh;
     pthread_mutex_unlock( &mutex1 );
}
int removeFromCharIOQueue()
{

     int returnVal;
     pthread_mutex_lock( &mutex1 );
     if (charIOHead == NULL)
         returnVal =  -1;
     else
     {
       node*n = charIOHead;
       charIOHead = charIOHead->next;
       returnVal = n->pid;
       delete n;
     }
     
     pthread_mutex_unlock( &mutex1 );
     return returnVal;

}
int removeFromBlockIOQueue()
{
     int returnVal;
     pthread_mutex_lock( &mutex2 );
     if (blockIOHead == NULL)
         returnVal =  -1;
     else
     {
       node*n = blockIOHead;
       blockIOHead = blockIOHead->next;
       returnVal = n->pid;
       delete n;
     }
       
     pthread_mutex_unlock( &mutex2 );
     return returnVal;
}

void addToBlockIOQueue(int pid)
{
     pthread_mutex_lock( &mutex2 );
     node_holder* nh;
     nh = addToQueue(blockIOHead,blockIOTail,pid);
     blockIOHead = nh->head;
     blockIOTail = nh->tail;
     delete nh;
     pthread_mutex_unlock( &mutex2 );
     
}

int removeFromCompletedIOQueue()
{
     int returnVal;
     pthread_mutex_lock( &mutex3 );
     if (completedHead == NULL)
         returnVal =  -1;
     else
     {
       node*n = completedHead;
       completedHead = completedHead->next;
       returnVal = n->pid;
       delete n;
     }
     
     pthread_mutex_unlock( &mutex3 );
     return returnVal;
}

void addToCompletedIOQueue(int pid)
{
     pthread_mutex_lock( &mutex3 );
     node_holder* nh;
     nh = addToQueue(completedHead,completedTail,pid);
     completedHead = nh->head;
     completedTail = nh->tail;
     delete nh;
     pthread_mutex_unlock( &mutex3 );

}

void charIODone()
{
     int pid = removeFromCharIOQueue();
     addToCompletedIOQueue(pid);
     printf("char IO done for : %d\n",pid);
}
void blockIODone()
{
     int pid = removeFromBlockIOQueue();
     addToCompletedIOQueue(pid);
     printf("Block IO done for : %d\n",pid);
     
     

}
void doCharacterIO()
{
  usleep(charIODelay);
  charIODone();
}
void* charIOThread(void* ptr)
{
     //
     printf("Char IO thread\n");
     while(true)
     {
       //check the charIOQueue to see if there is any pid waiting on it.
       if (charIOHead== NULL)
         usleep(clockDelay);
       else 
         doCharacterIO();
     }
}

void doBlockIO()
{
  usleep(blockIODelay);
  blockIODone();
}
void* blockIOThread(void* ptr)
{
     //
     printf("Block IO thread\n");
     while(true)
     {
       if (blockIOHead== NULL)
         usleep(clockDelay);
       else {
         doBlockIO();
         //cout << "After Block IO" << endl;
         }
     }
}

